ince 2015, JavaScript has improved immensely.
It’s much more pleasant to use it now than ever.
In this article, we’ll look at JavaScript generators.
Implementing Iterables via Generators
Iterables can be implemented with generators.
We can create an iterable object with the Symbol.iterable
method to make it iterable.
For instance, we can write:
const obj = {
*[Symbol.iterator]() {
yield 1;
yield 2;
yield 3;
}
}
We have the yield
keyword in our Symbol.iterator
method.
Then we can write:
for (const x of obj) {
console.log(x);
}
to iterate through the values, and we get:
1
2
3
obj[Symbol.iterator]
is a generator method that yields the values we can iterate through.
for-of use the method as an iterator to get the values.
Infinite Iterables
We can make itrerables that are infinite with generators.
For instance, we can write:
const obj = {
*[Symbol.iterator]() {
for (let n = 0;; n++) {
yield n;
}
}
}
function* take(n, iterable) {
for (const x of iterable) {
if (n <= 0) {
return;
}
n--;
yield x;
}
}
for (const x of take(5, obj)) {
console.log(x);
}
Our iterable obj
object has the Symbol.iterator
method that yields integers.
Then we created the take
function to yield the first n
items from the iterable
.
And finally, we loop through the returned variables.
Generators for Lazy Evaluation
Generators are useful for lazy evaluation.
For instance, we can create a function that yields the individual characters from a string.
We loop through the string and yield the keys.
For example, we can write:
function* tokenize(str) {
for (const s of str) {
yield s;
}
}
let c;
const gen = tokenize('foobar');
while (c = gen.next().value) {
console.log(c);
}
and we can get the values from the object.
Inheritance and Iterators
We can implement inheritance with generator functions like any other function.
For example, we can add an instance method to our generator by writing:
function* g() {}
g.prototype.foo = function() {
return 'bar'
};
const obj = g();
console.log(obj.foo());
We added the foo
method to the prototype
property.
Then we call g
generator function return the generator.
Then we can call the foo
method on it.
The console log should have 'bar'
logged.
If we try to get the prototype of an iterator.
For instance, we can write:
const getProto = Object.getPrototypeOf.bind(Object);
console.log(getProto([][Symbol.iterator]()));
Then we see the Array Iterator
object.
It has the next
method and other properties of the iterator’s prototype.
this
in Generators
Generator functions have their own value of this
.
It’s a function that sets up and returns a generator object.
And it contains the code that the generator object steps through.
We can look at the value of this
by writing:
function* gen() {
yield this;
}
const [genThis] = gen();
console.log(genThis)
If it’s at the top level and we get the value of this
as we did with destructuring, we get the window object.
This is assuming that strict mode is off.
If strict mode is on:
function* gen() {
'use strict';
yield this;
}
then genThis
is undefined
.
If our generator is in an object:
function* gen() {
'use strict';
yield this;
}
const obj = {
gen
}
const [genThis] = obj.gen();
console.log(genThis);
The genThis
is the object itself.
Generator functions are like traditional functions.
Conclusion
Iterables can be implemented with generators.
Also, we can implement inheritance and get the value of this
with generator functions.